﻿namespace SuperModel.BehaviorModel
{
    using System.Collections.Generic;
    using Castle.Core.Interceptor;

    /// <summary>
    /// Implementation of the Property Interceptor base class
    /// </summary>
    internal class PropertyInterceptor : IInterceptor
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyInterceptor"/> class. 
        /// </summary>
        public PropertyInterceptor()
        {
            BeforeGetActions = new Dictionary<string, List<IPropertyGetAction>>();
            AfterGetActions = new Dictionary<string, List<IPropertyGetAction>>();
            BeforeSetFilters = new Dictionary<string, List<IPropertySetFilter>>();
            AfterSetFilters = new Dictionary<string, List<IPropertySetFilter>>();
        }

        /// <summary>
        /// Gets the before-filters
        /// </summary>
        protected IDictionary<string, List<IPropertyGetAction>> BeforeGetActions { get; private set; }

        /// <summary>
        /// Gets the after-filters
        /// </summary>
        protected IDictionary<string, List<IPropertyGetAction>> AfterGetActions { get; private set; }
        
        /// <summary>
        /// Gets the before-filters
        /// </summary>
        protected IDictionary<string, List<IPropertySetFilter>> BeforeSetFilters { get; private set; }

        /// <summary>
        /// Gets the after-filters
        /// </summary>
        protected IDictionary<string, List<IPropertySetFilter>> AfterSetFilters { get; private set; }

        /// <summary>
        /// Adds a before-filter
        /// </summary>
        /// <param name="beforeFilter">The before filter</param>
        public void AddBefore(IPropertySetFilter beforeFilter)
        {
            AddItemToDictionary(beforeFilter, BeforeSetFilters);
        }

        /// <summary>
        /// Adds a before property get action
        /// </summary>
        /// <param name="beforeAction">
        /// The before action.
        /// </param>
        public void AddBefore(IPropertyGetAction beforeAction)
        {
            AddItemToDictionary(beforeAction, BeforeGetActions);
        }

        /// <summary>
        /// Adds an after-filter
        /// </summary>
        /// <param name="afterFilter">The after filter</param>
        public void AddAfter(IPropertySetFilter afterFilter)
        {
            AddItemToDictionary(afterFilter, AfterSetFilters);
        }
        
        /// <summary>
        /// Adds an after property get action
        /// </summary>
        /// <param name="afterAction">The action</param>
        public void AddAfter(IPropertyGetAction afterAction)
        {
            AddItemToDictionary(afterAction, AfterGetActions);
        }

        /// <summary>
        /// Perform the Interception
        /// </summary>
        /// <param name="invocation">The method invocation</param>
        public void Intercept(IInvocation invocation)
        {
            if (invocation.Method.Name.StartsWith("get_") && BeforeGetActions.Count + AfterGetActions.Count > 0)
            {
                string propertyName = invocation.Method.Name.Substring(4);
                CustomizeGet(invocation, propertyName);
            }
            else if (invocation.Method.Name.StartsWith("set_") && BeforeSetFilters.Count + AfterSetFilters.Count > 0)
            {
                string propertyName = invocation.Method.Name.Substring(4);
                CustomizeSet(invocation, propertyName);
            }
            else
            {
                // Pass through if there are no filters or the filters do not apply.
                invocation.Proceed();
            }
        }

        /// <summary>
        /// Adds an item to the dictionary
        /// </summary>
        /// <param name="item">
        /// The item to insert.
        /// </param>
        /// <param name="dictionary">
        /// The dictionary.
        /// </param>
        /// <typeparam name="T">
        /// The item type
        /// </typeparam>
        private static void AddItemToDictionary<T>(IPropertyInterceptor item, IDictionary<string, List<T>> dictionary)
            where T : IPropertyInterceptor
        {
            if (!dictionary.ContainsKey(item.PropertyName))
            {
                dictionary[item.PropertyName] = new List<T>();
            }

            dictionary[item.PropertyName].Add((T)item);
        }

        /// <summary>
        /// Customize a property set
        /// </summary>
        /// <param name="invocation">
        /// The invocation.
        /// </param>
        /// <param name="propertyName">
        /// The property Name.
        /// </param>
        private void CustomizeSet(IInvocation invocation, string propertyName)
        {
            bool keepFiltering = true;
            if (BeforeSetFilters.ContainsKey(propertyName))
            {
                foreach (IPropertySetFilter beforeFilter in BeforeSetFilters[propertyName])
                {
                    keepFiltering = beforeFilter.Execute(invocation, propertyName);
                    if (!keepFiltering)
                    {
                        break;
                    }
                }
            }

            if (keepFiltering)
            {
                invocation.Proceed();

                if (AfterSetFilters.ContainsKey(propertyName))
                {
                    foreach (IPropertySetFilter afterFilter in AfterSetFilters[propertyName])
                    {
                        keepFiltering = afterFilter.Execute(invocation, propertyName);
                        if (!keepFiltering)
                        {
                            break;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Customize a property get
        /// </summary>
        /// <param name="invocation">
        /// The invocation.
        /// </param>
        /// <param name="propertyName">
        /// The property Name.
        /// </param>
        private void CustomizeGet(IInvocation invocation, string propertyName)
        {
            if (BeforeGetActions.ContainsKey(propertyName))
            {
                foreach (IPropertyGetAction beforeFilter in BeforeGetActions[propertyName])
                {
                    beforeFilter.Execute(invocation, propertyName);
                }
            }

            invocation.Proceed();

            if (AfterGetActions.ContainsKey(propertyName))
            {
                foreach (IPropertyGetAction afterFilter in AfterGetActions[propertyName])
                {
                    afterFilter.Execute(invocation, propertyName);
                }
            }
        }
    }
}
